package com.hb.B_面向对象.A_类与继承

/**
 * Kotlin中没有new关键字去创建对象。
 *
 * 现在我们并没有给Bird类传入任何参数。现实中，你很可能因为需要传入不同的参数组合
 * 而在这种创建多个构造方法，在java中这是利用构造方法重载来实现的。
 * Java中这种方式存在两个缺点：
 *  1. 如果要支持任意参数组合来创建对象，那么需要实现的构造方法将会非常多
 *  2. 每个构造方法中的代码会存在冗余，如前两个构造方法都对age和color进行了相同的赋值操作
 *
 *  Kotlin通过引用构造语法来解决这些问题。
 *  1. 构造方法默认参数：
 *  要解决构造方法的问题，在kotlin中你可以给构造方法的参数指定默认值，从而避免不必要的方法重载。
 */
fun main() {
    var bird2 = Bird2(10.0);

    //指定参数
    var bird21 = Bird2(age = 10);

    //最终执行的是最后的那个初始化块
    println(bird2.weight)

    //场景init
    var bird22 = Bird2(color = "黄色");
    println(bird22.sex)
}

/**
 * 用一行代码就可以搞定，我们可以实现与java版本等价交换.
 * 构造方法的参数指定默认值编译后发现也是类的成员变量
 *
 * 我们在Bird类中可以使用var,val来声明构造方法的参数。这一方面代表了参数的引用可变性
 * 另一方面也是的我们在构造类重的语法得到了简化。
 *
 * 事实上，构造方法的参数名前当然可以没有val,var。然而带上他们之后就等价于在Bird类内部声明了一个同名的属性
 * 我们可以用this来进行调用。
 *
 * kotlin规定类中的所有非抽象属性成员都必须在对象创建时被初始化值。
 * 所以下面的sex必须被初始化值。
 *
 */
class Bird2(var weight:Double=200.0, color:String="blue",val age:Int=1){


    val sex:String
    fun FLy(){
        //全局可见
    }

    /**
     * kotlin中引用了init块
     * 它属于上述构造方法的一部分，两者在表现形式上却是分离的。Bird类的构造方法在类的外部，他只能对
     * 参数进行赋值。如果我们需要在初始化时进行其他的额外的操作，那么我们就可以使用init语句块来执行。
     * 每次创建对象的时候，这个语句块都会被执行一次
     */
    init {
        //只用声明为var的变量才可以进行修改
        //构造方法的参数可以在init语句块中被调用
        this.weight=10.0

        println("init start-1")
        println("init end-1")
    }

    /**
     * 当没有val或没有var的时候，构造方法的参数可以在init语句块被直接调用
     * 其实它们还可以用于初始化类内部的属性成员的情况。
     */
    val color:String=color

    /**
     * 以下是错误的，我们并不能再其他地方使用。
     * fun printColor(){
    println(color)
    }
     *
     */

    /**
     * 事实上，我们的构造方法还可以拥有多个init方法，它们会在对象呗创建的时候
     * 按照类中从上到下的顺序先后执行。
     *
     * 可以发现，多个init语句块有利于我们进一步对初始化的操作进行职能分离，
     * 这在复杂的业务开发中显得特别有用。
     *
     * 场景：
     *  我们创建一个类的对象时，很可能不需要对所有的属性都进行传值，其中存在一些特殊的属性
     *  比如鸟的性别，我们可以根据他的颜色来进行区分，所以他并不需要出现在构造方法的参数列表中
     *  有了init语句块的语法支持，很容易实现，假如黄色的鸟儿都是雌性，剩下的都是雄鸟。
     */

    init {
        this.weight=20.0
        println("init start-2")
        println("init end-2")
    }

    //init场景
    init {
        this.sex=if (this.color=="黄色") "雌性" else "雄性"
    }

    /**
     * kotlin规定类中的所有的非抽象属性成员都必须在对象创建时被初始化值。
     */
    fun printSex(){
        //下面是不被允许的，sex会被视为二次赋值，这对val声明的变量来说是不允许的
        //this.sex=if (this.color=="黄色") "雌性" else "雄性"
    }

    /**
     *解决printSex的问题，可以使用延迟初始化：by lazy和lateinit
     * 在kotlin中，我们主要使用by lazy和lateinit这两种语法来实现延迟初始化的效果。
     * 如果这是一个val声明的变量，我们可以使用by lazy来修饰.
     *
     * 总结by lazy的语法特点：
     * 1. 该变量必须是引用不可变的，而不能通过var 来修饰
     * 2. 在被首次调用的时候,才会进行赋值操作。一旦被赋值，后续它将不能被更改
     * lazy的背后接收一个lambda并返回一个Lazy<T>实例的函数，第一次访问该属性时，会执行lazy对应
     * 的lambda表达式并记录结果，后续访问该属性只是返回记录的结果。
     *
     * 另外系统会给lazy属性值默认加上同步锁，也就是LazyThreadSafeMode.SYNCHRONIZED,他在同一时刻
     * 只允许一个线程对lazy属性进行初始化，所以他是线程安全的。如果你能确认该属性可以并行执行，没有线程安全
     * 问题，那么可以给lazy传递LazyThreadSafetyMode.PUBLICATION参数。你还可以给lazy传递LazyThreadSafetyMode.NONE
     * 参数，这将不会有任何的线程方面的开销，当然也不会有任何线程安全的保证
     */



    val lazyParam:String by lazy {
        if (this.color=="黄色") "雌性" else "雄性"
    }

    val lazyParamParallelMode by lazy (LazyThreadSafetyMode.PUBLICATION){
        //并行模式
        if (this.color=="黄色") "雌性" else "雄性"
    }
    val lazyParamSerialMode by lazy (LazyThreadSafetyMode.NONE){
        //串行模式，不做任何的线程保证也不会有任何线程的开销问题
        if (this.color=="黄色") "雌性" else "雄性"
    }

    /**
     * lateinit主要用于var声明的变量，然而他不能用于基本数据类型，如Int,Long,我们需要用Integer这种
     * 包装类作为替代。lateinit可以解决之前的问题
     */
    //lateinitParam可以延迟初始化
    lateinit var lateinitParam:String
    fun printLateinitParam(){
        this.lateinitParam= if (this.color=="黄色") "雌性" else "雄性"
    }

    /**
     * 总而言之，Kotlin并不主张用Java中的构造方法重载，来解决多个构造参数组合调用的问题。
     * 取而代之的方案是利用构造参数默认值及用val和var来声明构造参数的语法，以更简洁的构造一个类
     * 的对象。
     */
}



